﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using Comunication;
using ServerDomain.CommandFolder;
using ServerDomain.Properties;

namespace ServerDomain
{
    public sealed class ServerInfo
    {
        object bandwithLock = new object();
        object serversLock = new object();
        private object clientsLock = new object();
        private object filesLock = new object();
        private object serverStateLock = new object();

        static readonly ServerInfo instance = new ServerInfo();

        static ServerInfo()
        {
        }

        ServerInfo()
        {
        }

        public static ServerInfo Instance
        {
            get
            {
                return instance;
            }
        }


        private IDictionary<string, ServerDTO> servers = new Dictionary<string, ServerDTO>();

        private IDictionary<IPEndPoint, Socket> clients = new Dictionary<IPEndPoint, Socket>();

        private IDictionary<string, string> files = new Dictionary<string, string>();

        private int _bandwith = ServerDomain.Properties.Settings.Default.ServerMaxBandwidth;

        private ServerStates _serverState;

        public void ChangeServerState(ServerStates states)
        {
            lock (serverStateLock)
            {
                _serverState = states;
            }
        }

        public int Bandwith
        {
            get
            {
                lock (bandwithLock)
                {
                    return _bandwith;
                }

            }
        }

        /// <summary>
        /// retorna el ancho de banda actual disponible para este servidor y un cliente
        /// </summary>
        /// <returns></returns>
        public int GetAvailableBandwith()
        {
            lock (bandwithLock)
            {
                int availableBandwith = Bandwith > Settings.Default.ClientMaxBandwidth
                                            ? Settings.Default.ClientMaxBandwidth
                                            : Bandwith;
                _bandwith = _bandwith - availableBandwith;

                IPAddress ipAddress = IPAddress.Parse(Settings.Default.MulticastAddress);
                IPEndPoint remoteep = new IPEndPoint(ipAddress, Convert.ToInt32(Settings.Default.MulticastPort));
                ServerServerNotifyBandwith serverServerNotifyBandwith = new ServerServerNotifyBandwith();
                UDPClient.SendUdpMessage(remoteep, serverServerNotifyBandwith);

                return availableBandwith;
            }
        }

        /// <summary>
        /// Verifica si el servidor actual es el adecuado para responder respecto al ancho de banda
        /// </summary>
        /// <returns></returns>
        public bool ServerIsAdequate()
        {
            lock (bandwithLock)
            {
                lock (serversLock)
                {
                    return servers.Values.All(serverDto => serverDto.Bandwith <= _bandwith);
                    //foreach (ServerDTO serverDto in servers.Values)
                    //{
                    //    if (serverDto.Bandwith > bandwith)
                    //    {
                    //        return false;
                    //    }
                    //}
                }
            }
        }

        public void AddClient(IPEndPoint key, Socket value)
        {
            lock (clientsLock)
            {
                if (!clients.ContainsKey(key))
                    clients.Add(key, value);
            }
        }

        /// <summary>
        /// Verifica si el archivo que se quiere subir existe o no ya en el servidor. En caso que exista y el MD5 coincida no se sube el archivo.
        /// </summary>
        /// <param name="md5"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public bool ExistsFile(string md5, string fileName)
        {
            lock (filesLock)
            {
                return files.ContainsKey(md5);
            }
        }

        /// <summary>
        /// Busca si existe el archivo a descargar, el nombre se puede dar tanto con o sin la extension por ello se valida eso
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public KeyValuePair<string, string>? ExistsFile(string fileName)
        {
            lock (filesLock)
            {
                foreach (KeyValuePair<string, string> file in
                    files.Where(file => file.Value.ToUpper() == fileName.ToUpper() || (!string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(fileName)) && (Path.GetFileNameWithoutExtension(fileName).ToUpper() == fileName.ToUpper()))))
                {
                    return file;
                }
                return null;
                //foreach (KeyValuePair<string, string> file in files)
                //{
                //    if (file.Value.ToUpper() == fileName.ToUpper() ||
                //        (!string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(fileName)) && (Path.GetFileNameWithoutExtension(fileName).ToUpper() == fileName.ToUpper())))
                //    {
                //        return file;
                //    }
                //}
                //return null;
            }
        }

        public int RestoreBandwith(int releaseBandwith)
        {
            lock (bandwithLock)
            {

                _bandwith = _bandwith + releaseBandwith;

                IPAddress ipAddress = IPAddress.Parse(Settings.Default.MulticastAddress);
                IPEndPoint remoteep = new IPEndPoint(ipAddress, Convert.ToInt32(Settings.Default.MulticastPort));
                ServerServerNotifyBandwith serverServerNotifyBandwith = new ServerServerNotifyBandwith();
                UDPClient.SendUdpMessage(remoteep, serverServerNotifyBandwith);

                return _bandwith;

            }
        }

        public void AddServer(IPEndPoint key, ServerDTO value)
        {
            lock (serversLock)
            {
                string stringKey = string.Format("{0}:{1}", key.Address.ToString(), key.Port.ToString());
                ServerDTO server;
                if (!servers.TryGetValue(stringKey, out server))
                {
                    servers.Add(stringKey, value);
                }
                else
                {
                    server.Bandwith = value.Bandwith;
                }
            }
        }

        public void UpdateServerBandwith(IPEndPoint key, int bandwith)
        {
            lock (serversLock)
            {
                string stringKey = string.Format("{0}:{1}", key.Address.ToString(), key.Port.ToString());
                ServerDTO server;
                if (servers.TryGetValue(stringKey, out server))
                {
                    server.Bandwith = bandwith;
                }

            }
        }

        public void AddFile(string md5, string fileName)
        {
            lock (filesLock)
            {
                if (!files.ContainsKey(md5))
                {
                    files.Add(md5, fileName);
                }
            }
        }

        public void LoadFiles()
        {
            lock (filesLock)
            {
                if (!Directory.Exists(Properties.Settings.Default.FolderPath))
                {
                    Directory.CreateDirectory(Properties.Settings.Default.FolderPath);
                }
                else
                {
                    string[] filePaths = Directory.GetFiles(Properties.Settings.Default.FolderPath);
                    foreach (string fileName in filePaths)
                    {
                        files.Add(Utiles.Utilidades.GetMd5HashFromFile(fileName), Path.GetFileName(fileName));
                    }

                }
            }
        }

        public ICollection<ServerDTO> GetServers()
        {
            lock (serversLock)
            {
                return servers.Values;
            }
        }

        public void RemoveFromServer(IPEndPoint ip)
        {
            lock (serversLock)
            {
                string stringKey = string.Format("{0}:{1}", ip.Address.ToString(), ip.Port.ToString());
                servers.Remove(stringKey);
            }
        }

        internal bool ExistsServer(IPEndPoint endPoint)
        {
            lock (serversLock)
            {
                string stringKey = string.Format("{0}:{1}", endPoint.Address.ToString(), endPoint.Port.ToString());
                return servers.ContainsKey(stringKey);
            }
        }
    }
}
